﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Animations;
using System.Drawing;
using System.Diagnostics;
using Silvermoon.Windows;
using System.Threading;

namespace Silvermoon.Controls.Beta
{
    /// <summary>
    /// Creates and controls animations for inserting and removing items for <see cref="ItemGroupBase"/>
    /// </summary>
    /// <remarks>
    /// For proper mixing of inserts and removals, it would be better to have two separate lists for inserts and removals and enumerating through the items starting with the
    /// list for inserts.
    /// </remarks>
    public class ItemGroupAnimator
    {
        #region classes

        const int max = 4096;

        abstract class ItemAnimation
        {
            public ItemAnimationType AnimationType { get; private set; }
            public int Progress;
            protected ItemGroupAnimator animator;
            public int Index;
            protected IntAnimation Animation;
            public ItemBase Item;

            public int Delay
            {
                get { return Animation.StartDelay; }
                set { Animation.StartDelay = value; }
            }


            protected GroupItemCollection Items { get { return animator.Items; } }
            protected ItemGroupBase Group { get { return animator.group; } }

            public ItemAnimation(ItemGroupAnimator animator, int index, ItemAnimationType type)
                : base()
            {
                this.AnimationType = type;
                this.animator = animator;
                this.Index = index;

                Animation = new IntAnimation(600, 0, max, 0);
                Animation.EasingFunction = SineEase.InOut;
                Animation.Progress += new EventHandler<AnimEventArgs<int>>(OnAnimationProgress);
                Animation.Completed += new EventHandler<AnimEventArgs<int>>(OnAnimationComplete);

                Item = Items[index];
            }


            void OnAnimationComplete(object sender, AnimEventArgs<int> e)
            {
                animator.itemAnimations.Remove(this);
                OnComplete();
                Group.InvalidateItems();
            }

            void OnAnimationProgress(object sender, AnimEventArgs<int> e)
            {
                OnProgress(e.Value);
                Group.InvalidateItems();
            }

            public void Stop()
            {
                Animation.Abort();
            }

            public void Invoke()
            {
                Animation.InvokeAsync();
            }

            protected abstract void OnComplete();

            protected virtual void OnProgress(int value)
            {
                Progress = value;
                //Item.SetAnimationState(this.AnimationType, value, max);
            }
        }

        class RemoveItemAnimation : ItemAnimation
        {

            public RemoveItemAnimation(ItemGroupAnimator animator, int index)
                : base(animator, index, ItemAnimationType.Remove)
            { }

            protected override void OnComplete()
            {
                Items.RemoveAt(Index);
            }
        }

        class InsertItemAnimation : ItemAnimation
        {
            public InsertItemAnimation(ItemGroupAnimator animator, int index)
                : base(animator, index, ItemAnimationType.Insert)
            { }

            protected override void OnComplete()
            {
            }

        }

        #endregion
        #region fields

        private ItemGroupBase group;
        private DblLinkedList<ItemAnimation> itemAnimations;

        #endregion
        #region ctor

        public ItemGroupAnimator(ItemGroupBase group)
            : base()
        {
            this.group = group;
        }

        #endregion
        #region props

        protected int ItemHeight
        {
            get { return group.ItemHeight; }
        }

        public bool IsAnimated
        {
            get { return itemAnimations != null && !itemAnimations.IsEmpty; }
        }

        protected GroupItemCollection Items { get { return group.Items; } }

        #endregion
        #region methods

        //public int AdjustItemIndex(int index)
        //{
        //    if (itemAnimations != null && !itemAnimations.IsEmpty)
        //    {
        //        int idx = index;
        //        foreach (ItemAnimation a in itemAnimations)
        //        {
        //            if (a.Index <= idx) index--;
        //        }
        //    }
        //    return index;
        //}

        //public int GetAnimOffset(int index)
        //{
        //    if (this.itemAnimations == null || itemAnimations.IsEmpty) return 0;
        //    int offset = 0;
        //    foreach (ItemAnimation a in itemAnimations)
        //    {
        //        if (a.Index <= index) offset += a.Item.Height * a.Progress / max;
        //    }
        //    return offset;
        //}

        /// <summary>
        /// Adds an item with animation.
        /// </summary>
        /// <param name="item">Item to insert.</param>
        /// <returns>True if inserted, otherwise false (a animated insert/remove might already be in progress for this index.)</returns>
        public bool AddAnimated(ItemBase item, int delay)
        {
            return InsertAnimated(Items.Count, item, delay);
        }

        /// <summary>
        /// Inserts an item with animation.
        /// </summary>
        /// <param name="index">Index to insert.</param>
        /// <param name="item">Item to insert.</param>
        /// <returns>True if inserted, otherwise false (a animated insert/remove might already be in progress for this index.)</returns>
        public bool InsertAnimated(int index, ItemBase item, int delay)
        {
            Items.Insert(index, item);

            if (itemAnimations == null) itemAnimations = new DblLinkedList<ItemAnimation>();
            ItemAnimation a = new InsertItemAnimation(this, index);
            int offset = GetOffset(index);

            // IMPORTANT: it's important to insert it at the first position, otherwise animation would not perform correctly if more than one animation is running:
            itemAnimations.AddFirst(a);
            a.Delay = delay;
            a.Invoke();
            return true;
        }

        /// <summary>
        /// Removes an item animated.
        /// </summary>
        /// <param name="index">Index of the item to remove.</param>
        public bool RemoveAnimated(int index)
        {
            ItemBase item = Items[index];
            return RemoveAnimated(index, item);
        }

        /// <summary>
        /// Removes an item animated.
        /// </summary>
        /// <param name="item">Item to remove.</param>
        public bool RemoveAnimated(ItemBase item)
        {
            int index = Items.IndexOf(item);
            if (index >= 0) return RemoveAnimated(index, item); else return false;
        }

        private ItemAnimation GetAnimation(int index)
        {
            if (itemAnimations != null)
            {
                foreach (ItemAnimation a in itemAnimations)
                {
                    if (a.Index == index) return a;
                }
            }
            return null;
        }

        private bool RemoveAnimated(int index, ItemBase item)
        {
            ItemAnimation a = GetAnimation(index);
            if (a != null)
            {
                InsertItemAnimation insertAnim = a as InsertItemAnimation;
                if (insertAnim != null)
                {
                    a.Stop();
                    a = null;
                }
            }
            if (a == null)
            {
                int offset = GetOffset(index);

                if (itemAnimations == null) itemAnimations = new DblLinkedList<ItemAnimation>();
                a = new RemoveItemAnimation(this, index);
                itemAnimations.AddLast(a);
                a.Invoke();
                return true;
            }
            else return false;
        }

        /// <summary>
        /// Updates currently running animations after an item was inserted.
        /// </summary>
        /// <param name="index">Index of item that was inserted.</param>
        public void UpdateInsertAnimations(int index)
        {
            if (itemAnimations != null)
            {
                foreach (ItemAnimation a in itemAnimations)
                {
                    if (a.Index >= index) a.Index++;
                }
            }
        }

        private int GetOffset(int index)
        {
            if (itemAnimations == null) return 0;
            int offset = 0;
            foreach (ItemAnimation a in itemAnimations)
            {
                if (a.Index < index) offset++;
            }
            return offset;
        }

        /// <summary>
        /// updates currently running animations after an item was removed.
        /// </summary>
        /// <param name="index">Index of item that was removed.</param>
        public void UpdateRemoveAnimations(int index)
        {
            if (itemAnimations != null)
            {
                //   ItemAnimation anim = null;
                foreach (ItemAnimation a in itemAnimations)
                {
                    if (a.Index > index)
                    {
                        a.Index--;
                    }
                    //       if (a.Index == index) anim = a;
                }
                //if (anim != null)
                //{
                //    anim.Stop();
                //    itemAnimations.Remove(anim);
                //}
            }
        }

        /// <summary>
        /// Aborts all running animations when the list was cleared.
        /// </summary>
        public void OnClear()
        {
            if (itemAnimations != null)
            {
                foreach (ItemAnimation a in itemAnimations) a.Stop();
                itemAnimations.Clear();
            }
        }

        public bool AdjustBounds(ItemBase item, int index, ref Rectangle bounds)
        {
            bool isSet = false;
            bool hasAnimations = this.itemAnimations != null && !itemAnimations.IsEmpty;
            if (hasAnimations)
            {
                int idx = index; ;
                foreach (ItemAnimation a in itemAnimations)
                {
                    if (a.Index < index)
                    {
                        InterpolateBounds(ref bounds, --idx, a);
                    }
                    if (a.Index == index)
                    {
                        isSet = true;
                        if (item != null) item.SetAnimationState(a.AnimationType, a.Progress, max, ref bounds);
                    }
                }
            }
            if (!isSet && item != null) item.SetAnimationState(ItemAnimationType.None, 0, 1, ref bounds);
            return hasAnimations;
        }

        public int AdjustHeight(int height)
        {
            if (this.itemAnimations != null && !itemAnimations.IsEmpty)
            {
                int index = Items.Count - 1;
                Rectangle bounds = new Rectangle();
                bounds.Location = GetLocation(index);
                foreach (ItemAnimation a in itemAnimations)
                {
                    InterpolateBounds(ref bounds, --index, a);
                }
                height = bounds.Top + ItemHeight;
            }
            return height;
        }


        private Point GetLocation(int index)
        {
            int n = group.ColumnCount;
            int r = index / n;
            int c = index % n;
            return new Point(c * group.ItemWidth, r * group.ItemHeight);
        }

        private void InterpolateBounds(ref Rectangle bounds, int index, ItemAnimation a)
        {
            int v = a.Progress;
            Point target = GetLocation(index);
            int m = max;
            if (a.AnimationType == ItemAnimationType.Insert) v = m - v;

            bounds.X += (target.X - bounds.X) * v / m;
            bounds.Y += (target.Y - bounds.Y) * v / m;

        }

        #endregion
    }
}
